﻿using System;
using System.Collections.Generic;
using System.Drawing;
using LevelEditor.Forms;
using LevelEditor.Document;
using System.Windows.Forms;

namespace LevelEditor.View
{
    public class DoUndoBucketTile : DoUndoNothing
    {
        #region Fields

        static readonly Point[] offsets = { new Point(-1, 0), new Point(1, 0), new Point(0, -1), new Point(0, 1) };
        
        int LayerIndex;

        Point StartPoint;

        uint UndoTileData;

        Rectangle TileSetSelection;

        List<Point> UndoDataLocations;

        bool canceled;

        ProgressDialog ResolveTiles, FillTiles, UnFillTiles;

        #endregion



        #region Constructors

        public DoUndoBucketTile(MainForm form, Point startPoint, Rectangle tileSetSelection):base(form)
        {
            #region data initialization
            canceled = false;
            UndoDataLocations = new List<Point>();
            LayerIndex = MainForm.CurrentLayer;
            StartPoint = startPoint;
            TileSetSelection = tileSetSelection;
            if (GetTileLocationIndexFromPoint(StartPoint) < MainForm.Level.Layers[LayerIndex].Tiles.Count)
                UndoTileData = MainForm.Level.Layers[LayerIndex].Tiles[GetTileLocationIndexFromPoint(StartPoint)];
            else
            {
                canceled = true;
                return;
            }
            UndoDataLocations.Add(StartPoint);
            #endregion

            ResolveTiles = new ProgressDialog();
            ResolveTiles.Text = "Bucket Fill";
            ResolveTiles.Description = "Caclulating tiles to fill: 0 tiles found.";
            ResolveTiles.BarStyle = ProgressBarStyle.Marquee;
            ResolveTiles.DoWork +=new System.ComponentModel.DoWorkEventHandler(ResolveTiles_DoWork);
            ResolveTiles.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(ResolveTiles_ProgressChanged);
            if (DialogResult.Cancel == ResolveTiles.ShowDialog(MainForm))
                canceled = true;
            ResolveTiles.Dispose();
        }

        void ResolveTiles_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
        {
            ResolveTiles.Description = "Caclulating tiles to fill: " + UndoDataLocations.Count + " tiles found.";
        }

        #endregion

        #region Helper Methods

        int GetTileDefinitionIndexFromPoint(Point point)
        {
            return point.X + point.Y * MainForm.Level.Layers[LayerIndex].TileSet.Size.Width;
        }

        int GetTileLocationIndexFromPoint(Point point)
        {
            return point.X + point.Y * MainForm.Level.Layers[LayerIndex].Size.Width;
        }

        #endregion

        #region Methods

        public override bool Do()
        {
            // check if user canceled
            if (canceled)
                return false;

            FillTiles = new ProgressDialog();
            FillTiles.ShowInTaskbar = true;
            FillTiles.Text = "Bucket Fill";
            FillTiles.Description = string.Format("{0:###.##}%({1} of {2} tiles filled)", 0, 0,
                UndoDataLocations.Count);
            FillTiles.DoWork += new System.ComponentModel.DoWorkEventHandler(FillTiles_DoWork);
            FillTiles.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(FillTiles_ProgressChanged);
            FillTiles.Cancelable = false;
            FillTiles.Maximum = UndoDataLocations.Count;

            // start filling
            FillTiles.ShowDialog(MainForm);
            FillTiles.Dispose();

            return base.Do();
        }

        void FillTiles_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
        {
            FillTiles.Description = string.Format("{0:###.##}%({1} of {2} tiles filled)",
                    (int)e.UserState * 100f / UndoDataLocations.Count, (int)e.UserState,
                    UndoDataLocations.Count);
            FillTiles.Value = (int)e.UserState;
        }

        public override void Undo()
        {
            UnFillTiles = new ProgressDialog();
            UnFillTiles.Text = "Bucket Fill";
            UnFillTiles.Description = string.Format("{0:###.##}%({1} of {2} tiles restored)", 0, 0,
                UndoDataLocations.Count);
            UnFillTiles.DoWork += new System.ComponentModel.DoWorkEventHandler(UnFillTiles_DoWork);
            UnFillTiles.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(UnFillTiles_ProgressChanged);
            UnFillTiles.Cancelable = false;
            UnFillTiles.Maximum = UndoDataLocations.Count;
            UnFillTiles.ShowDialog(MainForm);
            UnFillTiles.Dispose();
        }

        void UnFillTiles_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
        {
            UnFillTiles.Description = string.Format("{0:###.##}%({1} of {2} tiles restored)",
                        (int)e.UserState * 100f / UndoDataLocations.Count, (int)e.UserState,
                        UndoDataLocations.Count);
            UnFillTiles.Value = (int)e.UserState;
        }

        public override string ToString()
        {
            return "Bucket tile fill at " + StartPoint.ToString();
        }

        public override void Dispose()
        {
            base.Dispose();
        }


        #endregion

        #region Events

        void ResolveTiles_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            Queue<Point> queue = new Queue<Point>();
            queue.Enqueue(StartPoint);
            Layer Layer = MainForm.Level.Layers[LayerIndex];

            while (queue.Count != 0)
            {
                Point current = queue.Dequeue();
                for (int i = 0; i < offsets.Length; ++i)
                {
                    Point child = new Point(current.X + offsets[i].X, current.Y + offsets[i].Y);
                    // check position boundaries
                    if (child.X >= 0 && child.Y >= 0 &&
                        child.X < Layer.Size.Width &&
                        child.Y < Layer.Size.Height &&
                        // then check tile data
                        UndoTileData == Layer.Tiles[GetTileLocationIndexFromPoint(child)] &&
                        // then check for duplicates
                        !UndoDataLocations.Contains(child))
                    {
                        ResolveTiles.ReportProgress(0);
                        UndoDataLocations.Add(child);
                        queue.Enqueue(child);
                    }
                    if (ResolveTiles.Canceled)
                    {
                        canceled = true;
                        Cursor.Current = Cursors.Default;
                        return;
                    }
                }
            }
        }

        void UnFillTiles_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            int undocount = 0;
            foreach (Point p in UndoDataLocations)
            {
                undocount++;
                MainForm.Level.Layers[LayerIndex].Tiles[GetTileLocationIndexFromPoint(p)] = UndoTileData;
                UnFillTiles.ReportProgress(undocount * 100 / UndoDataLocations.Count, undocount);
            }
            MainForm.LayerViewControl.Invalidate();
        }

        void FillTiles_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            if (MainForm.CurrentLayer != LayerIndex)
            {
                MainForm.CurrentLayer = LayerIndex;
                MainForm.Palette.ResetToCurrentLayer(MainForm);
            }
            PaletteControl pc = (MainForm.Palette.pc as PaletteControl);
            int fillcount = 0;
            foreach (Point p in UndoDataLocations)
            {
                Point pointForIndex = Point.Empty;
                if (TileSetSelection.Width == 1 && TileSetSelection.Height == 1)
                    pointForIndex = TileSetSelection.Location;
                else
                {
                    int x = p.X - StartPoint.X;
                    int y = p.Y - StartPoint.Y;
                    if (x < 0)
                        x = (TileSetSelection.Width - 1) - Math.Abs(x + 1) % TileSetSelection.Width;
                    else
                        x = x % TileSetSelection.Width;
                    if (y < 0)
                        y = (TileSetSelection.Height - 1) - Math.Abs(y + 1) % TileSetSelection.Height;
                    else
                        y = y % TileSetSelection.Height;
                    pointForIndex.X = TileSetSelection.Location.X + x;
                    pointForIndex.Y = TileSetSelection.Location.Y + y;
                }
                MainForm.Level.Layers[LayerIndex].Tiles[p.X + p.Y * 
                    MainForm.Level.Layers[LayerIndex].Size.Width] = pc.GetValueFromTilePoint(pointForIndex);
                fillcount++;
                FillTiles.ReportProgress(fillcount * 100 / UndoDataLocations.Count, fillcount);
            }
            MainForm.LayerViewControl.Invalidate();
        }

        #endregion
    }
}
